# Runtime Hooks

## beforeInit

`SyncWaterfallHook`

Updates the corresponding init configuration before the MF instance is initialized.

* type

```ts
function beforeInit(args: BeforeInitOptions): BeforeInitOptions

type BeforeInitOptions ={
    userOptions: UserOptions;
    options: ModuleFederationRuntimeOptions;
    origin: ModuleFederation;
    shareInfo: ShareInfos;
}

interface ModuleFederationRuntimeOptions {
  id?: string;
  name: string;
  version?: string;
  remotes: Array<Remote>;
  shared: ShareInfos;
  plugins: Array<ModuleFederationRuntimePlugin>;
  inBrowser: boolean;
}
```

## init

`SyncHook`

Called after the MF instance is initialized.

* type

```ts
function init(args: InitOptions): void

type InitOptions ={
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
}
```

## beforeRequest

`AsyncWaterfallHook`

Called before resolving the remote path, useful for updating something before lookup.

* type

```ts
async function beforeRequest(args: BeforeRequestOptions): Promise<BeforeRequestOptions>

type BeforeRequestOptions ={
  id: string;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
}
```

## afterResolve

`AsyncWaterfallHook`

Called after resolving the remote path, allowing modification of the resolved content.

* type

```ts
async function afterResolve(args: AfterResolveOptions): Promise<AfterResolveOptions>

type AfterResolveOptions ={
  id: string;
  pkgNameOrAlias: string;
  expose: string;
  remote: Remote;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
}
```

## onLoad

`AsyncHook`

Triggered once a federated module is loaded, allowing access and modification to the exports of the loaded file.

* type

```ts
async function onLoad(args: OnLoadOptions): Promise<void>

type OnLoadOptions ={
  id: string;
  expose: string;
  pkgNameOrAlias: string;
  remote: Remote;
  options: ModuleOptions;
  origin: ModuleFederation;
  exposeModule: any;
  exposeModuleFactory: any;
  moduleInstance: Module;
}

type ModuleOptions = {
    remoteInfo: RemoteInfo;
    host: ModuleFederation;
}

interface RemoteInfo {
  name: string;
  version?: string;
  buildVersion?: string;
  entry: string;
  type: RemoteEntryType;
  entryGlobalName: string;
  shareScope: string;
}
```

## handlePreloadModule

`SyncHook`

Handles the preloading logic for remotes.

* type

```ts
function handlePreloadModule(args: HandlePreloadModuleOptions): void

type HandlePreloadModuleOptions ={
  id: string;
  name: string;
  remoteSnapshot: ModuleInfo;
  preloadConfig: PreloadRemoteArgs;
}
```

## errorLoadRemote

`AsyncHook`

Called if loading remotes fails, enabling custom error handling. Can return a custom fallback logic.

* type

```ts
async function errorLoadRemote(args: ErrorLoadRemoteOptions): Promise<void | unknown>

type ErrorLoadRemoteOptions ={
  id: string;
  error: unknown;
  options?: any;
  from: 'build' | 'runtime';
  lifecycle: 'beforeRequest' | 'beforeLoadShare' | 'afterResolve' | 'onLoad';
  origin: ModuleFederation;
}
```

The `lifecycle` parameter indicates the stage where the error occurred:

- `beforeRequest`: Error during initial request processing
- `afterResolve`: Error during manifest loading (most common for network failures)
- `onLoad`: Error during module loading and execution
- `beforeLoadShare`: Error during shared dependency loading

* example

```ts
import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const fallbackPlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'fallback-plugin',
      errorLoadRemote(args) {
        const { lifecycle, id, error } = args;

        // Log error details safely
        if (error) {
          console.warn(`Failed to load remote ${id} at ${lifecycle}:`, error?.message || error);
        }

        // Handle different error types based on lifecycle
        switch (lifecycle) {
          case 'afterResolve':
            // Manifest loading failed - provide fallback manifest or alternative URL
            return {
              id: id || 'fallback',
              name: id || 'fallback',
              metaData: { /* fallback manifest */ },
              shared: [],
              remotes: [],
              exposes: []
            };

          case 'beforeRequest':
            // Request processing failed - can return modified args or void
            console.warn(`Request processing failed for ${id}`);
            return void 0;

          case 'onLoad':
            // Module loading failed - provide fallback component
            return () => ({
              __esModule: true,
              default: () => 'Fallback Component'
            });

          case 'beforeLoadShare':
            // Shared dependency loading failed - return fallback shared module
            console.warn(`Shared dependency loading failed for ${id}`);
            return () => ({
              __esModule: true,
              default: {}
            });

          default:
            // Unknown lifecycle - log and return void
            console.warn(`Unknown lifecycle ${lifecycle} for ${id}`);
            return void 0;
        }
      },
    };
  };

const mf = createInstance({
    name: 'mf_host',
    remotes: [
        {
            name: "remote",
            alias: "app1",
            entry: "http://localhost:2001/mf-manifest.json"
        }
    ],
    plugins: [fallbackPlugin()]
});

mf.loadRemote('app1/un-existed-module').then(mod=>{
  expect(mod).toEqual('fallback');
});
```

## beforeLoadShare

`AsyncWaterfallHook`

Called before loading shared, can be used to modify the corresponding shared configuration.

* type

```ts
async function beforeLoadShare(args: BeforeLoadShareOptions): Promise<BeforeLoadShareOptions>

type BeforeLoadShareOptions ={
  pkgName: string;
  shareInfo?: Shared;
  shared: Options['shared'];
  origin: ModuleFederation;
}
```

## resolveShare

`SyncWaterfallHook`

Allows manual setting of the actual shared module to be used.

* type

```ts
function resolveShare(args: ResolveShareOptions): ResolveShareOptions

type ResolveShareOptions ={
  shareScopeMap: ShareScopeMap;
  scope: string;
  pkgName: string;
  version: string;
  GlobalFederation: Federation;
  resolver: () => Shared | undefined;
}
```

* example

```ts
import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'

import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const customSharedPlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'custom-shared-plugin',
      resolveShare(args) {
        const { shareScopeMap, scope, pkgName, version, GlobalFederation } = args;

        if (
          pkgName !== 'react'
        ) {
          return args;
        }

        args.resolver = function () {
          shareScopeMap[scope][pkgName][version] = window.React; // replace local share scope manually with desired module
          return shareScopeMap[scope][pkgName][version];
        };
        return args;
      },
    };
  };


const mf = createInstance({
    name: 'mf_host',
    shared: {
      react: {
        version: '17.0.0',
        scope: 'default',
        lib: () => React,
        shareConfig: {
          singleton: true,
          requiredVersion: '^17.0.0',
        },
      },
    },
    plugins: [customSharedPlugin()]
});

window.React = ()=> 'Desired Shared';

mf.loadShare("react").then((reactFactory)=>{
  expect(reactFactory()).toEqual(window.React());
});
```

## beforePreloadRemote

`AsyncHook`

Called before the preload handler executes any preload logic.

* type

```ts
async function beforePreloadRemote(args: BeforePreloadRemoteOptions): BeforePreloadRemoteOptions

type BeforePreloadRemoteOptions ={
  preloadOps: Array<PreloadRemoteArgs>;
  options: Options;
  origin: ModuleFederation;
}
```

## generatePreloadAssets

`AsyncHook`

Used to control the generation of resources that need to be preloaded.

* type

```ts
async function generatePreloadAssets(args: GeneratePreloadAssetsOptions): Promise<PreloadAssets>

type GeneratePreloadAssetsOptions ={
  origin: ModuleFederation;
  preloadOptions: PreloadOptions[number];
  remote: Remote;
  remoteInfo: RemoteInfo;
  remoteSnapshot: ModuleInfo;
  globalSnapshot: GlobalModuleInfo;
}

interface PreloadAssets {
  cssAssets: Array<string>;
  jsAssetsWithoutEntry: Array<string>;
  entryAssets: Array<EntryAssets>;
}
```

## createScript

`SyncHook`

Used to modify the script when loading resources.

* type

```ts
function createScript(args: CreateScriptOptions): HTMLScriptElement | void

type CreateScriptOptions ={
  url: string;
}
```

* example

```ts
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'change-script-attribute',
      createScript({ url }) {
        if (url === 'http://localhost:3001/remoteEntry.js') {
          let script = document.createElement('script');
          script.src = url;
          script.setAttribute('loader-hooks', 'isTrue');
          script.setAttribute('crossorigin', 'anonymous');
          return script;
        }
      }
    };
  };
```

## fetch
The `fetch` function allows customizing the request that fetches the manifest JSON. A successful `Response` must yield a valid JSON.

`AsyncHook`

- **Type**

```typescript
function fetch(manifestUrl: string, requestInit: RequestInit): Promise<Response> | void | false;
```

- Example for including the credentials when fetching the manifest JSON:

```typescript
// fetch-manifest-with-credentials-plugin.ts
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

export default function (): FederationRuntimePlugin {
  return {
    name: 'fetch-manifest-with-credentials-plugin',
    fetch(manifestUrl, requestInit) {
      return fetch(manifestUrl, {
        ...requestInit,
        credentials: 'include'
      });
    },
  }
};
```

## loadEntry
The `loadEntry` function allows for full customization of remotes, enabling you to extend and create new remote types. The following two simple examples demonstrate loading JSON data and module delegation.

`asyncHook`

- **Type**

```typescript
function loadEntry(args: LoadEntryOptions): RemoteEntryExports | void;

type LoadEntryOptions = {
  createScriptHook: SyncHook,
  remoteEntryExports?: RemoteEntryExports,
  remoteInfo: RemoteInfo
};
interface RemoteInfo {
  name: string;
  version?: string;
  buildVersion?: string;
  entry: string;
  type: RemoteEntryType;
  entryGlobalName: string;
  shareScope: string;
}
export type RemoteEntryExports = {
  get: (id: string) => () => Promise<Module>;
  init: (
    shareScope: ShareScopeMap[string],
    initScope?: InitScope,
    remoteEntryInitOPtions?: RemoteEntryInitOptions,
  ) => void | Promise<void>;
};
```

- Example Loading JSON Data

```typescript
// load-json-data-plugin.ts
import { init } from '@module-federation/enhanced/runtime';
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
  return {
    name: 'load-json-data-plugin',
    loadEntry({ remoteInfo }) {
      if (remoteInfo.jsonA === "jsonA") {
        return {
          init(shareScope, initScope, remoteEntryInitOPtions) {},
          async get(path) {
            const json = await fetch(remoteInfo.entry + ".json").then(res => res.json())
            return () => ({
              path,
              json
            })
          }
        }
      }
    },
  };
};
```
```ts
// module-federation-config
{
  remotes: {
    jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package"
  }
}
```
```ts
// src/bootstrap.js
import jsonA from "jsonA"
jsonA // {...json data}
```

- Example Delegate Modules

```typescript
// delegate-modules-plugin.ts
import { init } from '@module-federation/enhanced/runtime';
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
  return {
    name: 'delegate-modules-plugin',
    loadEntry({ remoteInfo }) {
      if (remoteInfo.name === "delegateModulesA") {
        return {
          init(shareScope, initScope, remoteEntryInitOPtions) {},
          async get(path) {
            path = path.replace("./", "")
            const {[path]: factory} = await import("./delegateModulesA.js")
            const result = await factory()
            return () => result
          }
        }
      }
    },
  };
};
```
```ts
// ./src/delegateModulesA.js
export async function test1() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("test1 value")
    }, 3000)
  })
}
export async function test2() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("test2 value")
    }, 3000)
  })
}
```
```ts
// module-federation-config
{
  remotes: {
    delegateModulesA: "delegateModulesA@https://delegateModulesA.js"
  }
}
```
```ts
// src/bootstrap.js
import test1 from "delegateModulesA/test1"
import test2 from "delegateModulesA/test2"
test1 // "test1 value"
test2 // "test2 value"
```
